/*
 *	Copyright (C) 2008 CRIMERE
 *	Copyright (C) 2008 Jean-Marc Mercier
 *	
 *	This file is part of OTS (optimally transported schemes), an open-source library
 *	dedicated to scientific computing. http://code.google.com/p/optimally-transported-schemes/
 *
 *	CRIMERE makes no representations about the suitability of this
 *	software for any purpose. It is provided "as is" without express or
 *	implied warranty.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#if !defined(_FiniteDifferenceOperator_)
#define _FiniteDifferenceOperator_

#include <src/miscellaneous/OS_basic.h>
#include <src/math/Operators/weight_set_operator.h>
#include <src/data/Operators.h>
#pragma warning (disable: 4355)

template <class T>
struct FiniteDifferenceOperator_interface 
{
	typedef typename T interfaced_type;
	FiniteDifferenceOperator_interface() : call_back_(0) {};
	FiniteDifferenceOperator_interface(const interfaced_type* i) : call_back_(i)  {};
	virtual ~FiniteDifferenceOperator_interface(){};

	virtual typename T::result_type operator()(typename T::argument_type from) const 
	{
		typedef typename T::argument_base_type argument_base_type;
		typedef typename argument_base_type::value_type value_type; 
		typedef typename argument_base_type::reference reference; 
		typedef typename T::argument_base_type argument_base_type; 


		typedef typename T::result_type result_type; 
		typedef result_type::reference reference_result_type;
		typedef argument_base_type::const_iterator const_iterator;
		typedef result_type::iterator iterator;

		typedef typename interfaced_type::weight_set_type weight_set_type;

		typedef weight_set_type::const_iterator1 const_iterator1;
		typedef weight_set_type::const_iterator2 const_iterator2;

		result_type result(from);
		result.assign(0);
		iterator beg_r = result.begin();
		value_type local(*beg_r);

		const_iterator1 itl;
		const_iterator2 itl_;
		const_iterator1 end(call_back_->weight_set_.end1());
		for (itl = call_back_->weight_set_.begin1();itl != end;++itl) {
				reference_result_type r_i(*(beg_r+itl.index1() ) );
				const_iterator2 end2(itl.end());
				for (itl_ = itl.begin();itl_ != end2;++itl_) {
						local = ( *(beg_r+itl_.index2()) ) *(*itl_);
						r_i += local;
				};
				
		};
		return result;
	};
	virtual void output (std::ostream& o) const {
		o<<call_back_->weight_set_ << std::endl;
	};
	const interfaced_type* call_back_;
};


/* !
	Finite difference operator are Operator They are defined as linear operators : 
	\f[
		R_i = \sum_{j \in \nu_i} \omega_{i,j}S_j.
	\f]
*/
template <class A, class R, class V = smart_ptr<A> >
class FiniteDifferenceOperator  : 
	public weighted_operator<A,R,No_Interaction<A>, V>
	, public FiniteDifferenceOperator_interface<weighted_operator<A,R > >
{
public :
	typedef FiniteDifferenceOperator_interface											interface_type;
	typedef FiniteDifferenceOperator													type;
	typedef Finite_Diff_Operator														Family_Operator;
	typedef FiniteDifferenceOperator_interface<weighted_operator<A,R > >				Base;
	typedef typename Base																Base_Cast;

	type() : Base(*this) {};
	type(const weight_set_type & data) : interface_type(*this),  graph_base(data) {};
	type(const Base &first) : interface_type(*this), Base(first)
	{};
	type(const type & from) :  interface_type(*this) {
	};
	type & operator = (const type & from) {
		Base::operator =(from);
		return *this;
	};
	virtual ~FiniteDifferenceOperator(){};
	virtual smart_ptr_type Create_Instance() const {
		return Create_Instance_helper_arg<type,weight_set_type>(&weight_set_);
	};
};



#endif
